package server

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"sort"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/spf13/viper"
	"github.com/vulsio/go-cve-dictionary/db"
	log "github.com/vulsio/go-cve-dictionary/log"
	"golang.org/x/xerrors"
)

// Start starts CVE dictionary HTTP Server.
func Start(logToFile bool, logDir string, driver db.DB) error {
	e := echo.New()
	e.Debug = viper.GetBool("debug")

	// Middleware
	e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{Output: os.Stderr}))
	e.Use(middleware.Recover())

	// setup access logger
	if logToFile {
		logPath := filepath.Join(logDir, "access.log")
		f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
		if err != nil {
			return xerrors.Errorf("Failed to open a log file: %s", err)
		}
		defer f.Close()
		e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{Output: f}))
	}

	// Routes
	e.GET("/health", health())
	e.GET("/cves/:id", getCve(driver))
	e.POST("/cpes", getCveByCpeName(driver))
	e.POST("/cpes/ids", getCveIDsByCpeName(driver))

	bindURL := fmt.Sprintf("%s:%s", viper.GetString("bind"), viper.GetString("port"))
	log.Infof("Listening on %s", bindURL)

	return e.Start(bindURL)
}

// Handler
func health() echo.HandlerFunc {
	return func(c echo.Context) error {
		return c.String(http.StatusOK, "")
	}
}

// Handler
func getCve(driver db.DB) echo.HandlerFunc {
	return func(c echo.Context) error {
		cveid := c.Param("id")
		cveDetail, err := driver.Get(cveid)
		if err != nil {
			log.Errorf("%s", err)
			return err
		}
		return c.JSON(http.StatusOK, &cveDetail)
	}
}

type cpeName struct {
	Name string `form:"name"`
}

func getCveByCpeName(driver db.DB) echo.HandlerFunc {
	return func(c echo.Context) error {
		cpe := cpeName{}
		err := c.Bind(&cpe)
		if err != nil {
			log.Errorf("%s", err)
			return err
		}
		cveDetails, err := driver.GetByCpeURI(cpe.Name)
		if err != nil {
			log.Errorf("%s", err)
			return err
		}

		sort.Slice(cveDetails, func(i, j int) bool {
			return cveDetails[i].CveID < cveDetails[j].CveID
		})
		return c.JSON(http.StatusOK, &cveDetails)
	}
}

func getCveIDsByCpeName(driver db.DB) echo.HandlerFunc {
	return func(c echo.Context) error {
		cpe := cpeName{}
		err := c.Bind(&cpe)
		if err != nil {
			log.Errorf("%s", err)
			return err
		}
		nvds, jvns, err := driver.GetCveIDsByCpeURI(cpe.Name)
		if err != nil {
			log.Errorf("%s", err)
			return err
		}
		cveIDs := map[string][]string{"NVD": nvds, "JVN": jvns}
		return c.JSON(http.StatusOK, &cveIDs)
	}
}
